/* File: lab3_steg0.c
* ------------------
* Start file for Assignment #3, PTek, autumn 2014.
* Author: CES
* Last modified: 2014-12-16
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "genlib.h"
#include "simpio.h"
#include "strlib.h"

/*
* Constant: QUIT
* --------------
* The constant QUIT is to break the program
* from the main menu.
*/

#define QUIT -1

/*
* Constant: INITIAL_SIZE
* ----------------------
* The constant defines the initial
* size of the dynamic array holding
* the cat data.
*/

#define INITIAL_SIZE 10

/* Constant: MAX_COLOURS
* ---------------------
* The constant defines the maximum number
* of colours that one cat can have.
*/

#define MAX_COLOURS 5

/* Type definition: catT
* ---------------------
* The catT is a simple struct containing all the
* attributes of the cats. The colours attribute
* is an array, since a cat can have several colours.
*/

typedef struct {
	int id;
	string name;
	char gender;
	int birthYear;
	int numberOfColours;
	string colours[MAX_COLOURS];
} catT;

/* Type definition: DBT
* --------------------
* The DBT type is a pointer to a struct containing a dynamic
* array of catT and three integer counters: one to keep track of the
* current size of the catT vector, one to keep track of the
* current number of cats in the database and another to keep track
* of the next available id number.
*/

typedef struct{
	catT *cats;
	int currentMaxSize;
	int currentNumberOfCats;
	int nextId;
} *DBT;

catT catsInRegister[INITIAL_SIZE];


DBT initializeDataBase(void);
void loadDataBase(string, DBT);

void printIntroMessage(void);
void executeCommands(DBT);

string getRegisterFileName(void);


void addCat(DBT);
void removeCat(DBT);
int catRemoval(DBT dataBase, int catId);
void updateCat(DBT, int);

void listCat(DBT);
void listAllCats(DBT);
void listOneCat(DBT, int);
void listOneCatAndItsAttributes(DBT, int);
void listAllCatsAndTheirAttributes(DBT);

void saveDataBase(string, DBT);


void sortRegister(DBT);
void showStatistics(DBT);

void getCatNameFromUser(DBT, int);
void getCatBirthYearFromUser(DBT, int);
void getCatColoursFromUser(DBT, int);
void getCatGenderFromUser(DBT, int);
int getNameOrId(DBT);

void arrayMemoryReallocation(DBT);

int structComparisonBirthYear(const void *a, const void *b);
int structComparisonName(const void *a, const void *b);

/* Function definition: void main(void)
Usage:main()
* --------------------
The main funcion of the program that calls all the functions of the program
to let the user utilize the register program.*/

void main(void)
{
	DBT dataBase;
	string fileName;
	dataBase = initializeDataBase();
	printIntroMessage();
	fileName = getRegisterFileName();
	loadDataBase(fileName, dataBase);
	executeCommands(dataBase);
	saveDataBase(fileName, dataBase);
}

/* Function definition: void initializeDataBase(void)
Usage:initializeDataBase()
* --------------------
This function intializes the database, creating a dynamic array of typ catT,
sets the currentNumberOfCats to zero and nextID to 1.*/

DBT initializeDataBase(void)
{
	DBT dataBase;

	dataBase = New(DBT);
	dataBase->currentNumberOfCats = 0;
	dataBase->currentMaxSize = INITIAL_SIZE;
	dataBase->nextId = 1;
	dataBase->cats = NewArray(INITIAL_SIZE, catT);
	return dataBase;
}

/* Function definition: void loadDataBase(void)
Usage:loadDataBase(string, DBT)
* --------------------
This function loads a file into the dbt struct and if there is no existing textfile one will be created.*/
void loadDataBase(string fileName, DBT database){

	FILE* inFile;
	FILE* outFile;
	int i, x;
	string registerCheck;


	inFile = fopen(fileName, "r");

	if (!inFile){
		outFile = fopen(fileName, "w");
		printf("\n\nCreating new file: %s.\n\n", fileName);
	}
	else{
		registerCheck = (ReadLine(inFile));
		if (registerCheck == NULL)
		{
			printf("\n\nThe register is empty.\n\n\n"); 
			exit;
		}
		else database->currentNumberOfCats = StringToInteger(registerCheck);
		database->nextId = database->currentNumberOfCats + 1;
		if (database->currentNumberOfCats > INITIAL_SIZE){
			arrayMemoryReallocation(database);
		}
		else database->currentMaxSize = INITIAL_SIZE;
		for (i = 0; i < database->currentNumberOfCats; i++)
		{
			if (database->currentNumberOfCats == 0) break;
			database->cats[i].id = StringToInteger(ReadLine(inFile));
			database->cats[i].name = ReadLine(inFile);
			database->cats[i].gender = IthChar(ReadLine(inFile), 0);
			database->cats[i].birthYear = StringToInteger(ReadLine(inFile));
			database->cats[i].numberOfColours = StringToInteger(ReadLine(inFile));
			for (x = 0; x < database->cats[i].numberOfColours; x++)
			{
				database->cats[i].colours[x] = ReadLine(inFile);
			}
		}
	}

}

/* Function definition: void printIntroMessage(void)
	Usage:printIntroMessage()
	* --------------------
	This function simply clears the console screen then prints the programs intro message.
*/
void printIntroMessage(void)
{
	system("cls");
	printf("This program helps organize information about cats and their attributes.\n\n");
	printf("You may add and remove cats and their attributes, list the names of cats\n");
	printf("currently in the organizer, listthe cats and their attributes, look up\n");
	printf("the attributes of an individual cat, get statistics from the register,\n");
	printf("or sort the register.\n\n\n");
}
/* Function definition: void executeCommands(DBT)
	Usage:executeCommands(DBT)
	* --------------------
	This function takes in the selected command from the user and launches the appropriate funcion.
*/
void executeCommands(DBT dataBase)
{
	int command;

	while (TRUE)
	{
		printf("1. Add a cat and it's attributes.\n");
		printf("2. Update the entry for a cat.\n");
		printf("3. Remove a cat and it's attrinutes.\n");
		printf("4. List all cats.\n");
		printf("5. List all cats and their attributes.\n");
		printf("6. Look up the attributes of a cat.\n");
		printf("7. List statistics from the register.\n");
		printf("8. Sort register.\n\n");
		printf("0. To exit the program and save the database.\n");

		command = GetInteger();

		system("cls");

		switch (command)
		{
		case 1:
			addCat(dataBase);
			break;
		case 2:
			updateCat(dataBase, getNameOrId(dataBase));
			break;
		case 3:
			removeCat(dataBase);
			break;
		case 4:
			listAllCats(dataBase);
			break;
		case 5:
			listAllCatsAndTheirAttributes(dataBase);
			break;
		case 6:
			listCat(dataBase);
			break;
		case 7:
			showStatistics(dataBase);
			break;
		case 8:
			sortRegister(dataBase);
			break;
		case 0: break;
		default: printf("Invalid input, please try again!\n");
		}
		if (command == 0) { break; }
	}
}

/* Function definition: string getRegisterFileName(void)
	Usage:loadDataBase()
	* --------------------
	This function takes in a filename from the user which later on will be opened to load information from and store information to.
*/

string getRegisterFileName(void)
{
	string fileName;

	printf("Enter text file name.\n");

	fileName = GetLine();
	return fileName;
}

/* Function definition: void saveDataBase(string, DBT)
	Usage:saveDataBase(string, DBT)
	* --------------------
	This function saves the database to the file that the user input.
*/

void saveDataBase(string fileName, DBT dataBase)
{
	FILE* outFile;
	int i, j;

	outFile = fopen(fileName, "w");
	fprintf(outFile, "%d\n", dataBase->currentNumberOfCats);
	for (i = 0; i < dataBase->currentNumberOfCats; i++)
	{
		fprintf(outFile, "%d\n", dataBase->cats[i].id);
		fprintf(outFile, "%s\n", dataBase->cats[i].name);
		fprintf(outFile, "%c\n", dataBase->cats[i].gender);
		fprintf(outFile, "%d\n", dataBase->cats[i].birthYear);
		fprintf(outFile, "%d\n", dataBase->cats[i].numberOfColours);
		for (j = 0; j < dataBase->cats[i].numberOfColours; j++){
			if (i <= dataBase->currentNumberOfCats && j == dataBase->cats[i].numberOfColours){
				fprintf(outFile, "%s", dataBase->cats[i].colours[j]);
			}
			else{
				fprintf(outFile, "%s\n", dataBase->cats[i].colours[j]);
			}
		}
	}
	fclose(outFile);
}

/* Function definition: void addCat(DBT)
	Usage:loadDataBase(DBT)
	* --------------------
	This function let's the user add another cat to the register.
*/

void addCat(DBT dataBase){
	int newCat;
	newCat = dataBase->currentNumberOfCats;
	dataBase->currentNumberOfCats += 1;
	if (dataBase->currentNumberOfCats >= dataBase->currentMaxSize) arrayMemoryReallocation(dataBase);
	dataBase->cats[newCat].id = dataBase->nextId;

	getCatNameFromUser(dataBase, newCat);
	getCatGenderFromUser(dataBase, newCat);
	getCatBirthYearFromUser(dataBase, newCat);
	getCatColoursFromUser(dataBase, newCat);
	dataBase->nextId += 1;
}
/* Function definition: void updateCat(DBT, int)
Usage:loadDataBase(DBT, int)
* --------------------
This funvyion let's the user update the attributes of an already existing cat.*/

void updateCat(DBT dataBase, int catToUpdate){
	catToUpdate -= 1;

	getCatNameFromUser(dataBase, catToUpdate);
	getCatBirthYearFromUser(dataBase, catToUpdate);
	getCatColoursFromUser(dataBase, catToUpdate);
}

/*	Function: getCatNameFromUser(DBT dataBase, int catID)
	Usage: getCatNameFromUser(DBT dataBase, int catID)
	-----------------------------------------------
	Takes in the struct DBT and an ID of a cat,
	ask the user to input the name of the cat.
*/

void getCatNameFromUser(DBT dataBase, int catID){
	int i, counter = 0;
	string catName;

	printf("Please enter the name of the cat: ");
	while (TRUE){
		catName = GetLine();
		catName = ConvertToUpperCase(catName);
		for (i = 0; i < StringLength(catName); i++){
			if (isalpha(IthChar(catName, i))){
				counter++;
			}
			else{
				printf("The name may only contain letters, please try again\n\n");
				break;
			}
		}
		if (counter == StringLength(catName)) { break; }
		counter = 0;
	}
	dataBase->cats[catID].name = catName;
}
/*	Function: getCatGenderFromUser(DBT dataBase, int catID)
	Usage: getCatGenderFromUser(dataBase, catID)
	-----------------------------------------------
	Takes in the struct DBT and an ID of a cat,
	ask the user to input the gender of the cat.
*/
void getCatGenderFromUser(DBT dataBase, int catID)
{
	string oldCatGender;
	char catGender;

	printf("Please enter the cats gender (F) for female or (M) for male: ");
	while (TRUE){
		oldCatGender = GetLine();
		catGender = IthChar(oldCatGender, 0);

		if (catGender == 'M' || catGender == 'F'){ break; }
		else if (catGender == 'm'){
			catGender = 'M';
			break;
		}
		else if (catGender == 'f'){
			catGender = 'F';
			break;
		}
		else { printf("Wrong character, please enter the cats gender 'F' for female or 'M' for male.\n\n"); } 
	}
	dataBase->cats[catID].gender = catGender;
}

/*	Function: getCatBirthYearFromUser(DBT dataBase, int catID)
Usage: getCatBirthYearFromUser(dataBase, catID)
-----------------------------------------------
Takes in the struct DBT and an ID of a cat,
ask the user to input the year the cat was born.
*/

void getCatBirthYearFromUser(DBT dataBase, int catID)
{
	int catBirthYear;

	printf("Please enter the cats birthyear: ");
	while (TRUE){
		catBirthYear = GetInteger();

		if (catBirthYear > 2015 || catBirthYear < 0){
			printf("Please enter a valid year.\n\n");
		}
		else{ break; }
	}
	dataBase->cats[catID].birthYear = catBirthYear;
}

/*	Function: getCatColoursFromUser(DBT dataBase, int catID)
Usage: getCatColoursFromUser(dataBase, catID)
-----------------------------------------------
Takes in the struct DBT and an ID of a cat,
ask the user to input the amount of colors the cat
have and what colors the cat have.
*/

void getCatColoursFromUser(DBT dataBase, int catID)
{
	int i, counter = 0, j;
	string catColor;

	dataBase->cats[catID].numberOfColours = 0;

	printf("Finally enter the colours of the cat. Up to 5 colors, finish with '0'.\n");
	for (i = 0; i < MAX_COLOURS; i++){
		while (TRUE){
			catColor = GetLine();

			if (IthChar(catColor, 0) == '0') { break; }

			catColor = ConvertToUpperCase(catColor);
			for (j = 0; j < StringLength(catColor); j++){
				if (isalpha(IthChar(catColor, j))){
					counter++;
				}
				else{
					printf("The color may only contain letters, please try again\n\n");
					break;
				}
			}
			if (counter >= StringLength(catColor)) {
				counter = 0;
				break;
			}
		}

		if (IthChar(catColor, 0) == '0') { break; }

		dataBase->cats[catID].colours[i] = catColor;
		dataBase->cats[catID].numberOfColours++;
	}
}

/*	Function: listAllCats(DBT dataBase)
Usage: listAllCats(dataBase)
-----------------------------------------------
Takes in the struct DBT and then calls for
listOneCat for the ammount of times there are
cats in the register.
*/

void listAllCats(DBT dataBase)
{
	int i;

	for (i = 0; i < dataBase->currentNumberOfCats; i++){
		listOneCat(dataBase, i);
	}
}

/*	Function: listCat(DBT dataBase)
	Usage: listCat( dataBase)
	-----------------------------------------------
	Takes in the struct DBT and asks the user what cat
	he/she wants to print out. Then calls listOneCatAndItsAttributes
	to print the cat and its attributes.
*/

void listCat(DBT dataBase)
{

	listOneCatAndItsAttributes(dataBase, getNameOrId(dataBase));
}

/*	Function: listAllCatsAndTheirAttributes(DBT dataBase)
	Usage: listAllCatsAndTheirAttributes(dataBase)
	-----------------------------------------------
	Takes in the struct DBT and then calls the
	listOneCatAndItsAttributes to print all the cats
	in the register.
*/

void listAllCatsAndTheirAttributes(DBT dataBase)
{
	int i = 0;

	while (i < dataBase->currentNumberOfCats){
		listOneCatAndItsAttributes(dataBase, i);
		i++;
	}
}

/*	Function: listOneCatAndItsAttributes(DBT dataBase, int catToList)
	Usage: listOneCatAndItsAttributes(dataBase, catToList)
	-----------------------------------------------
	Takes in the struct DBT and an ID for the cat
	that wants to be listed, then prints the name and
	all the cats attributes.
*/

void listOneCatAndItsAttributes(DBT dataBase, int catToList)
{
	int i;

	printf("Cat Name: %s\n", dataBase->cats[catToList].name);
	printf("Cat ID: %d\n", dataBase->cats[catToList].id);
	printf("Cat Gender: %c\n", dataBase->cats[catToList].gender);
	printf("Cat BirthYear: %d\n", dataBase->cats[catToList].birthYear);
	printf("Cat Number Of Colours: %d\n", dataBase->cats[catToList].numberOfColours);
	printf("Cat colours: ");
	for (i = 0; i < dataBase->cats[catToList].numberOfColours; i++){
		printf("%s ", dataBase->cats[catToList].colours[i]);
	}

	printf("\n\n");
}

/*	Function: listOneCat(DBT dataBase, int catToList)
	Usage: listOneCat(dataBase, catToList);
	-----------------------------------------------
	Takes in the struct DBT and an ID for the cat
	that wants to be listed, then prints the name and
	the ID of the cat.
*/

void listOneCat(DBT dataBase, int catToList)
{

	printf("Cat Name: %s\n", dataBase->cats[catToList].name);
	printf("Cat ID: %d\n", dataBase->cats[catToList].id);

	printf("\n\n");
}

/*	Function: removeCat(DBT dataBase)
	Usage: removeCat(dataBase);
	-----------------------------------------------
	Takes in the struct DBT then asks the user
	what cat to remove, removes the cat and then
	places the remaining cats in order.
*/

void removeCat(DBT dataBase)
{
	int i, j;


	for (i = getNameOrId(dataBase); i < dataBase->currentNumberOfCats; i++){
		if (dataBase->cats[i].id == NULL){ break; }

		dataBase->cats[i - 1].name = dataBase->cats[i].name;
		dataBase->cats[i - 1].gender = dataBase->cats[i].gender;
		dataBase->cats[i - 1].birthYear = dataBase->cats[i].birthYear;
		dataBase->cats[i - 1].numberOfColours = dataBase->cats[i].numberOfColours;
		for (j = 0; j < dataBase->cats[i - 1].numberOfColours; j++){
			dataBase->cats[i - 1].colours[i] = dataBase->cats[i].colours[j];
		}
	}
	dataBase->currentNumberOfCats--;
}

/*	Function: showStatistics(DBT dataBase)
	Usage: showStatistics(dataBase);
	-----------------------------------------------
	Takes in the struct DBT then prints out the youngest
	cat, the oldest cat and the amount of female and male
	cats.
*/

void showStatistics(DBT dataBase)
{
	int lowestCatAge, highestCatAge, numberOfMales = 0, numberOfFemales = 0, i;
	lowestCatAge = dataBase->cats[0].birthYear;
	highestCatAge = dataBase->cats[0].birthYear;

	for (i = 0; i < dataBase->currentNumberOfCats; i++)
	{
		if (lowestCatAge > dataBase->cats[i].birthYear) lowestCatAge = dataBase->cats[i].birthYear;
		if (highestCatAge < dataBase->cats[i].birthYear) highestCatAge = dataBase->cats[i].birthYear;
		if (dataBase->cats[i].gender == 'F')numberOfFemales++;
		else numberOfMales++;
	}
	printf("The number of cats in the register is %d\n", dataBase->currentNumberOfCats);
	printf("The lowest cat age is %d\n", lowestCatAge);
	printf("The highest cat Age is %d\n", highestCatAge);
	printf("The number of female cats are: %d\nand the number of male cats are: %d\n", numberOfFemales, numberOfMales);
}

/*	Function: sortRegister(DBT dataBase, int sortAlternative)
	Usage: showStatistics(DBT, int);
	-----------------------------------------------
	This functions sorts the array of cats
	either by name or birthyear which the user decides.
*/

void sortRegister(DBT dataBase){

	int sortAlternative;

	printf("Do you wish to sort the register by cat name(1), or birthyear(2)? ");

	while (TRUE){
		sortAlternative = GetInteger();
		if (sortAlternative == 1 || sortAlternative == 2){ break; }
		else { printf("Invalid choice, please try again.\n"); }
	}

	if (sortAlternative == 1){
		qsort(dataBase->cats, dataBase->currentNumberOfCats, sizeof(catT), structComparisonName);
	}
	else
	{
		qsort(dataBase->cats, dataBase->currentNumberOfCats, sizeof(catT), structComparisonBirthYear);
	}
}

/*	Function: arrayMemoryReallocation
	Usage: arrayMemoryReallocation(dataBase);
	-----------------------------------------------
	Makes the memoryfor cats* twice as big.
*/

void arrayMemoryReallocation(DBT dataBase)
{
	catT *catArray;
	int i;
	catArray = NewArray(2 * sizeof(dataBase->cats), catT);

	for (i = 0; i < dataBase->currentNumberOfCats; i++);
	{
		catArray[i] = dataBase->cats[i];
	}
	free(dataBase->cats);
	dataBase->cats = catArray;
	dataBase->currentMaxSize *= 2;
}

/*	Function: void getNameOrId(int *pId, int *pName)
	Usage: getNameOrId(pId, pName)
	-----------------------------------------------
	Takes in the DBT and returns the index where the 
	cat is.
*/

int getNameOrId(DBT dataBase){
	int i, j, byId;
	int counter = 0;
	string getNameOrId;
	catT *temp;

	temp = NewArray(sizeof(dataBase->cats), catT);

	printf("Enter the name or the ID of the cat: ");

	while (TRUE){
		getNameOrId = GetLine();
		for (i = 0; i <= StringLength(getNameOrId); i++){
			if (isalpha(IthChar(getNameOrId, i))) { continue; }
			else { break; }
		}
		if (i == StringLength(getNameOrId)){
			getNameOrId = ConvertToUpperCase(getNameOrId);
			for (j = 0; j < dataBase->currentNumberOfCats; j++){
				if (StringEqual(getNameOrId, dataBase->cats[j].name)){

					temp[j] = dataBase->cats[j];
					counter++;
				}
			}
			if (counter < 1){

				printf("Please choose the cat by id. \n\n");
				for (j = 0; j < counter; j++){
					listOneCatAndItsAttributes(temp, j);

				}
				byId = GetInteger();
				return byId;
			}
			else{
				
				return temp[0].id;
			}
		}

		for (i = 0; i <= StringLength(getNameOrId); i++){
			if (isdigit(IthChar(getNameOrId, i))) { continue; }
			else { break; }
		}

		if (i == StringLength(getNameOrId)){
			for (j = 0; j < dataBase->currentNumberOfCats; j++){
				if (StringToInteger(getNameOrId) == dataBase->cats[j].id){
					return j;
				}
			}
		}

		printf("\nEnter a valid name or ID of the cat.\n\n");
	}
}

int structComparisonBirthYear(const void *a, const void *b)
{
	catT x, y;
	x = *(catT *)a;
	y = *(catT *)b;
	if (x.birthYear == y.birthYear) return 0;
	else if (x.birthYear>y.birthYear) return 1;
	else return -1;
}

int structComparisonName(const void *a, const void *b)
{
	catT x, y;
	x = *(catT *)a;
	y = *(catT *)b;
	return (StringCompare(x.name, y.name));
}
